2. OOP高级编程¶
本章高级编程中所谓的高级,指的是相对比较抽象,也比较实用的关于类的技术,但对于非强编程的专业,比如数据分析,办公弄自动化等, 可能暂时并不需要, 所以对这一部分同学暂时不推荐学习, 可能您根本不需要,所以我们统一放到了高级编程中.
2.1. 类属性(propety)¶
类的属性值(attribute)经常需要被方位, 通常的访问为get
和set
两个, 即对属性进行复制和读取属性.
如下面案例,我们对属性 name
的访问不想让外部直接访问, 这涉及对内容的封装,此时可以通过两个函数setName
和
getName
来完成, 这就避免了但对属性的直接访问:
# 属性案例
# 创建Student类,描述学生类
# 学生具有Student.name属性
# 但name格式并不统一
# 可以用增加一个函数,然后自动调用的方式,但很蠢
class Student():
def __init__(self, name, age):
self.name = name
self.age = age
# 如果不想修改代码
self.setName(name)
# 介绍下自己
def intro(self):
print("Hai, my name is {0}".format(self.name))
def setName(self, name):
self.name = name.upper()
s1 = Student("LIU Ying", 19.8)
s2 = Student("michi stangle", 24.0)
s1.intro()
s2.intro()
访问结果如下:
Hai, my name is LIU YING
Hai, my name is MICHI STANGLE
此时虽然我们给出了避免类的属性直接被访问的方式, 即通过函数访问,但并不能避免属性真的被外部访问,至少语法上不能避免,比如一样可以 直接通过:
print(s1.name)
来访问属性.
为了避免此类情况发生,我们可以对属性进行改名,比如添加一个下划线_
来表明这个属性不希望被外部直接访问,但此种方式只是一个标记,或者叫
约定俗成,如果我访问, 还是没有语法问题的.
2.1.1. 类属性值(property)¶
主要为了满足以下需求,我们设计了类的属性(property)这个功能:
封装类的属性值(attribute), 不许外部访问此类内容
对类的属性(attribute)赋值的时候, 可能我们有额外的需求,例如一些额外的操作
此时我们可以用一个函数来代替属性值(attribute)
对一个变量的操作,一般具有赋值,一般也就赋值,读取,删除三个操作,所以使用属性(properety)需要的大致步骤是:
定义三个函数,即对这个属性(property)进行赋值,读取, 删除时候的操作功能
把这三个函数绑定到一个变量名称上, 此后对这个变量名称的访问即调用对于的函数.
具体案例参看下面, 案例中,如果访问Person
的name
和age
属性, 我们希望带有额外的操作, 比如自动进行一些大小写转换
等, 此时可以把name
等设置成property
来进行操作:
# peroperty案例
# 定义一个Person类,具有name,age属性
# 对于任意输入的姓名,我们希望都用大写方式保存
# 年龄,我们希望内部统一用整数保存
# x = property(fget, fset, fdel, doc)
class Person():
'''
这是一个人,一个高尚的人,一个脱离了低级趣味的人
还他妈的有属性
'''
# 函数的名称可以任意
# 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值
def fget(self):
return self._name * 2
# 对于对name进行赋值时候的操作, 即默认把值转换成大写
def fset(self, name):
# 所有输入的姓名以大写形式保存
self._name = name.upper()
# 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串
def fdel(self):
self._name = "NoName"
# 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上,
# 以后对name操作即调用相应三个操作函数
name = property(fget, fset, fdel, "对name进行下下操作啦")
# 作业:
# 1. 在用户输入年龄的时候,可以输入整数,小数,浮点数
# 2. 但内部为了数据清洁,我们统一需要保存整数,直接舍去小数点
# 调用类
p1 = Person()
p1.name = "TuLing" # 赋值, 调用fset
print(p1.name) # 读取,调用fget
运行结果如下所示:
TULINGTULING
2.1.2. @property
¶
对上述属性系统还给提供了装饰器来完成相应的工作
单独对某个函数进行修饰后, 则此函数名称就是一个具备只读功能的属性(property), 注意此属性
只能读取,不能有别的功能, 例如下面案例,如果对属性year
进行除了读取以外操作, 报错!
代码如下, 代码中只用property
修饰, 则属性只具有只读性质:
class Person():
def __init__(self):
self._age = 18
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = "北京图灵学院" + name
@property
def year(self):
return 2022 - self._age
p = Person( )
# 读取year属性
print(p.year)
# year为只读属性, 尝试其他操作报错
p.year = 1997
执行上述代码后, 报错如下:
Connected to pydev debugger (build 211.7142.13)
2004
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 26, in <module>
p.year = 1997
AttributeError: can't set attribute
python-BaseException
如果想让定义的属性property
具有其他能力,比如赋值删除等, 则需要继续时候用装饰器来完成,参考
下面代码,定义的属性name
具有删除,赋值,读取功能:
class Person():
def __init__(self):
self._name = "NoName"
@property
def name(self):
return self._name
@name.setter
def name(self, n):
self._name = n.upper()
@name.deleter
def name(self):
self._name = "IsDeleted"
p = Person( )
# 读取name属性
print(p.name)
# 调用setter
p.name = "dana liu"
print(p.name)
# 调用deleter
del p.name
# 调用getter
print(p.name)
代码运行如下:
/Users/bbb/anaconda3/bin/python3 /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py --multiproc --qt-support=auto --client 127.0.0.1 --port 62518 --file /Users/bbb/baoshu/book/python/new_python/code/tt.py
Connected to pydev debugger (build 211.7142.13)
NoName
DANA LIU
IsDeleted
从上面案例也可以基本推断出, 所谓装饰器就是对上一小节中属性函数的简单包装, 在使用中也是 对赋值,读取,删除三个函数调用而已.
属性(property)的命名务必跟类中使用的属性(attribute)不能相同,否则会造成递归调用,自己想想点解?
2.2. 类的内置属性¶
类会内置了很多属性,通过这些属性方便我们对类的使用和控制, 一般此类内置都以双下划线开头结尾, 我们介绍四个内置属性如下:
__dict__
: 类或者实例的所有属性(是所有,包括attribute, 函数, property等)__doc__
: 类的文档__name__
: 类的名称__bases__
: 类的父类
上面知识点以类Person
为例, 类的实现代码如下:
class Animal():
pass
class Person(Animal):
'''
这是一个人,一个高尚的人,一个脱离了低级趣味的人
还他妈的有属性
'''
# 函数的名称可以任意
# 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值
def fget(self):
return self._name * 2
# 对于对name进行赋值时候的操作, 即默认把值转换成大写
def fset(self, name):
# 所有输入的姓名以大写形式保存
self._name = name.upper()
# 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串
def fdel(self):
self._name = "NoName"
# 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上,
# 以后对name操作即调用相应三个操作函数
name = property(fget, fset, fdel, "对name进行下下操作啦")
def __init__(self):
self.p_name = "NoName"
self.age = 19
2.2.1. __dict__
¶
如果查看类或者实例的内容, 则可以直接按下面代码打印:
p = Person( )
print(Animal.__dict__)
print("=" * 30)
print(Person.__dict__ )
print("=" * 30)
print( p.__dict__ )
打印结果如下:
Connected to pydev debugger (build 211.7142.13)
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
==============================
{'__module__': '__main__', '__doc__': '\n 这是一个人,一个高尚的人,一个脱离了低级趣味的人\n 还他妈的有属性\n ', 'fget': <function Person.fget at 0x7f8465dce510>, 'fset': <function Person.fset at 0x7f8465dce730>, 'fdel': <function Person.fdel at 0x7f8465dce7b8>, 'name': <property object at 0x7f8465dcac78>, '__init__': <function Person.__init__ at 0x7f8465dce840>}
==============================
{'p_name': 'NoName', 'age': 19}
这里类的属性和实例的属性进行了严格的区分, 这两者内容也大不一样 子类和父类也各自维持各自的
__dict__
内容 内置类型list,tuple,dict等没有__dict__
属性
可以简单理解成:
实例的
__dict__
包含的是类定义的时候前面有self.
的内容,例如self.age
类的
__dict__
包含的是出去实例__dict__
之外的所有
2.2.2. __name__
¶
__name__
可以用来代表一段代码的名称,我们在写代码的时候经常写一个入口程序:
if __name__ == "__main__":
pass
作为一段段代码的第一行执行程序, 就是要检查这段代码的名称是否是一个值.
在类中我们对类调用__name__
属性的时候代表的是这个类的名字, 不如下面代码:
p = Person( )
print(Animal.__name__)
print("=" * 30)
print(Person.__name__ )
print("=" * 30)
print(__name__)
print("=" * 30)
print(p.__name__)
代码执行结果如下:
Connected to pydev debugger (build 211.7142.13)
Animal
==============================
Person
==============================
__main__
==============================
python-BaseException
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 45, in <module>
print(p.__name__)
AttributeError: 'Person' object has no attribute '__name__'
代码最后一行执行保存, 因为实例变量p
没有__name__
属性
2.2.3. __bases__
/ __base__
¶
用来表示一个类的父类和父类的列表.
参见下面代码:
p = Person( )
print(Person.__base__)
print(Person.__bases__)
执行结果如下:
Connected to pydev debugger (build 211.7142.13)
<class '__main__.Animal'>
(<class '__main__.Animal'>,)
2.3. 魔法函数¶
类的魔法函数是指一类特殊的函数:
此类函数由类定义名称, 不可以随意命名
一般在某个时机会被自动调用,不需要手动调用
每个魔法函数都对于一个固定的调用时机, 通常也默认有固定的的功能和写法, 如果实现, 则时机到了会被自动调用
名称由前后双下划线包裹
我们以前介绍的构造函数就是一个魔法函数, 类实例化的时候被调用,具有特定的写法和命名等.
本章我们介绍其他常用的魔法函数.
2.3.1. __call__
¶
实例可以直接被调用, 此时会执行__call__
函数:
class A():
def __init__(self, name = 0):
print("哈哈,我被调用了")
def __call__(self):
print("我被调用了again")
定义后用下面两行代码,第一行生成实例,第二行直接运行实例,此时调用魔法函数:
a = A()
a()
运行结果如下:
哈哈,我被调用了
我被调用了again
2.3.2. __str__
¶
当实例被需要当做字符串处理的时候会调用此函数, 如果没有实现此函数会执行默认功能.
此函数要求返回一个字符串.
class A():
def __init__(self, name = 0):
print("哈哈,我被调用了")
def __call__(self):
print("我被调用了again")
def __str__(self):
return "图灵学院的例子"
a = A()
print(a)
在执行print(a)
的时候, 需要把实例转成一个字符串,此时会执行上面的魔法函数, 上面魔法函数返回的内容
就是打印出来的内容.
哈哈,我被调用了
图灵学院的例子
2.3.3. __geattr__
¶
调用某个属性的时候, 如果这个属性不存在, 会自动执行这个魔法函数.
魔法函数的返回值作为不存在的这个属性的值.
class A():
name = "NoName"
age = 18
def __getattr__(self, name):
print("没找到呀没找到")
print(name)
定义完后执行下面代码:
a = A()
print(a.name)
print(a.addr)
# 作业:
# 为什么会打印第四句话,而且第四句话是打印的 None
# 答案在QQ群9990960
执行结果如下:
NoName
没找到呀没找到
addr
None
2.3.4. __setattr__
¶
当给类的属性进行赋值的时候, 自动调用此函数.
需要注意此函数的写法,比较特殊 魔法函数的很多写法都比较特殊,性质决定
参考案例:
class Person():
def __init__(self):
pass
def __setattr__(self, name, value):
print("设置属性: {0}".format(name))
# 下面语句会导致问题,死循环
#self.name = value
# 此种情况,为了避免死循环,规定统一调用父类魔法函数
super().__setattr__(name, value)
调用上面定义的类:
p = Person()
print(p.__dict__)
p.age = 18
print(p.__dict__)
结果如下:
Connected to pydev debugger (build 211.7142.13)
{}
设置属性: age
{'age': 18}
2.3.5. __gt__
¶
此类魔法函数在比较两个类实例的时候会被自动调用,除了大于,小于,还有大于等于,小于等于, 等于,不等于等.
参看下面代码, 如果比较两个学生类的实例, 则以魔法函数返回的结果为比较的结果:
class Student():
def __init__(self, name):
self._name = name
# 比较两个实例按名称比较
def __gt__(self, obj):
print("哈哈, {0} 会比 {1} 大吗?".format(self, obj))
return self._name > obj._name
调用代码如下:
# 作业:
# 字符串的比较是按什么规则
stu1 = Student("one")
stu2 = Student("two")
print(stu1 > stu2)
执行结果如下:
# 作业:
# 下面显示结果不太美观,能否改成形如 "哈哈, one 会比 two 大吗?“
```
哈哈, <__main__.Student object at 0x7f4aac6b3b00> 会比 <__main__.Student object at 0x7f4aac6b3ac8> 大吗?
False
2.4. 抽象类¶
有些类不需要定义实例, 这些类存在的意义就是被继承, 这些类我们可以定义成抽象类,即只需要被继承不能够 被实例化的类.
定义抽象类需要借助
abc
模块, 即抽象类是abc.ABCMeta
的子类.抽象类包含至少一个抽象函数, 此时需要用到装饰器
@abc.abstractxxxxx
来定义一个抽象函数, 此类 函数不需要有实现代码, 只能被继承, 然后在子类中实现具体功能一个包含抽象函数的类就是抽象类, 哪怕只包含一个抽象函数, 则这个类就是抽象的,不能被实例化
@abc.abstractxxxx
装饰器只是说你这个函数是的性质,属于抽象的且是xxxx函数,包括abstractmethode
: 普通抽象函数abstractclassmethode
:抽象类函数abstractstaticmethode
: 抽象静态函数abstractproperty
: 抽象属性
参看代码,从一个了一个人类, 因为人类具有抽象函数(还不止一个), 所以是一个抽象类
# 抽象类的实现
import abc
#声明一个类并且指定当前类的元类
class Human(metaclass=abc.ABCMeta):
# 定义一个抽象的方法
@abc.abstractmethod
def smoking(self):
pass
# 定义类抽象方法
@abc.abstractclassmethod
def drink():
pass
# 定义静态抽象方法
@abc.abstractstaticmethod
def play():
pass
def sleep(self):
print("Sleeping.......")
此时直接用Human
实例化是会报错的:
Connected to pydev debugger (build 211.7142.13)
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 22, in <module>
h = Human()
TypeError: Can't instantiate abstract class Human with abstract methods drink, play, smoking
python-BaseException
2.5. 元类¶
2.5.1. 手动组装类¶
函数可以当做换一个变量处理,此时函数可以当做值来传递,这是我们在函数中已经给大家讲过的内容, 在类中定义的函数,包括 实例函数,类函数, 静态函数等,都可以看做一个变量来处理,即我们可以灵活的组装一个类.
参看下面代码, 我们定义了一个类A
, 但没有具体内容, 同时定义了函数say
, 这就是一个普通函数,我们可以单独调用,
也可以把这个函数作为值传递给A
中的某一个变量, 此时可以把函数say
作为类的函数调用,同时在A
的实例调用的时候,
第一个参数会自动把自己传入, 此时如果想把say
当做实例函数,必须至少有一个参数,否则报错:
class A():
pass
def say(self):
print("Saying... ...")
class B():
def say(self):
print("Saying......")
# 单独调用say'必须有对参数,否则报错
say(9)
# 给类A添加一个实例函数say
A.say = say
a = A()
a.say()
# 打印出A的内容
print(A.__dict__)
上述代码运行结果如下:
onnected to pydev debugger (build 211.7142.13) Saying… … Saying… … {‘module’: ‘main’, ‘dict’: <attribute ‘dict’ of ‘A’ objects>, ‘weakref’: <attribute ‘weakref’ of ‘A’ objects>, ‘doc’: None, ‘say’: <function say at 0x7f83e28e6620>}
可以看出, 我们通过后期的赋值,可以随意赋予一个类以函数
这类赋值不可以直接对类实例进行,例如下面代码:
class A():
pass
def say(self):
print("Saying... ...")
a1 = A()
a2 = A()
a1.say = say
# 此时say作为a`是一个普通函数, 不是类中定义的
# 调用可以这样
a1.say(a1)
# 但如果是按照实例方法调用则报错
a1.say()
运行结果如下:
Connected to pydev debugger (build 211.7142.13)
Saying... ...
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 19, in <module>
a1.say()
TypeError: say() missing 1 required positional argument: 'self'
python-BaseException
2.5.2. MethodType
组装类¶
MethodType
方法可以给实例对象或类绑定方法, 功能同上面全手动类似.
下面代码给实例利用MethodType
增加函数功能, 但此时并没有真正修改类A
的内容, 所以对实例a2
修改后
面的代码a1,a3
再次调用say
就会报错.
# 自己组装一个类
from types import MethodType
class A():
pass
class B():
pass
def say(self):
print("Saying... ...")
a1 = A()
a2 = A()
a2.say = MethodType(say, A)
a2.say()
a3 = A()
# 以下使用都会报错, 报错信息为A没有say属性
a3.say()
a1.say()
代码运行后结果如下:
Connected to pydev debugger (build 211.7142.13)
Saying... ...
python-BaseException
Traceback (most recent call last):
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 24, in <module>
a3.say()
AttributeError: 'A' object has no attribute 'say'Vk
如果想直接修改类的定义内容,则需要直接给类赋值,代码如下:
# 自己组装一个类
from types import MethodType
class A():
pass
def say(self):
print("Saying... ...")
A.say = MethodType(say, A)
a1 = A()
a2 = A()
a3 = A()
a1.say()
a2.say()
a3.say()
上面代码修改了类的内容,以后由这个类产生的实例都会具备相应功能, 运行结果如下:
Connected to pydev debugger (build 211.7142.13)
Saying... ...
Saying... ...
Saying... ...
2.5.3. 利用type
来创造类¶
python
中只有type
才是唯一真神, 他可以创建万物,包括类.
我们可以利用这来完全定义类, 参看下代码:
# 先定义类应该具有的成员函数
def say(self):
print("Saying.....")
def talk(self):
print("Talking .....")
# 用type来创建一个类
# thype的参数含义, 1,类名称, 2.类的父类们, 3, 类的内容, 字典形式
A = type("AName", (object,), {"class_say": say, "class_talk": talk})
# 然后可以像正常访问一样使用类
a = A()
a.class_say()
a.class_talk()
print(a.class_say)
print(a.class_talk)
上例中,我们用type
无中生有了一个类, 同时用这个类创建实例并运行, 味道好极了, 运行结果如下:
Connected to pydev debugger (build 211.7142.13)
Saying.....
Talking .....
<bound method say of <__main__.AName object at 0x7fe70cfe6be0>>
<bound method talk of <__main__.AName object at 0x7fe70cfe6be0>>
2.5.4. 元类¶
元类是负责创建类的类, 叫做元类.
我们上面使用各种方法, 理论上也可以创建
一个类,但元类是专业负责创建类的一个有着特殊写法的类.
元类写法固定:
必须继承自
type
一般以
MetaClass
结尾实现内容和步骤有规定,要实现
__new__
魔法函数
其实元类就是一个批量创建类的模板
参看下面代码, 我们实现一个元类, 负责创建其余的类.
# 元类演示
# 元类写法是固定的,必须继承自type
# 元类一般命名以MetaClass结尾
class TulingMetaClass(type):
# 注意以下写法
def __new__(cls, name, bases, attrs):
# 自己的业务处理
print("哈哈,我是元类呀")
attrs['id'] = '000000'
attrs['addr'] = "北京海淀区公主坟西翠路12号"
attrs['douyin'] = "liudana"
attrs['wechat'] = "131 191 44 223"
# 固定返回
return type.__new__(cls, name, bases, attrs)
# 元类定义完就可以使用,使用注意写法
# 必须表明metaclass属性, 即指明利用哪个元类创建这个类
class Teacher(object, metaclass=TulingMetaClass):
pass
t = Teacher()
print(t.id)
print(t.douyin)
print(t.wechat)
上面代码运行解雇如下:
Connected to pydev debugger (build 211.7142.13)
哈哈,我是元类呀
000000
liudana
131 191 44 223
2.6. 迷信(Mixin)¶
Mixin
这种设计模式表达的意思是掺入.
python
类的血统比较乱,不拒绝多继承,这就导致一些继承方面的问题(菱形继承), 一个人爸爸太多了总有种说不出的感觉, 代码也是,
让人混乱,为了解决此类问题, 我们不在推荐退继承, 但需要功能拓展怎么办? 我们用迷信(Mixin)处理.
迷信的宗旨就是把父类和子类的耦合关系降到最低,甚至是零, 迷信本质上还是父类,但这个父类本质上只负责处理业务, 完全做到了跟子类除了 业务外代码没任何耦合度(或者尽量减少耦合度).
Mixin
的使用原则是:
首先他必须表示某一单一功能,而不是某个物品
职责必须单一,如果由多个功能,则写多个
Mixin
Mixin
不能依赖于子类的实现子类即使没有继承这个
Mixin
类, 也能照样工作,只是缺少了某个功能Mixin类中不应该显式的出现__init__初始化方法
Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现
Mixin类的祖先类也应是Mixin类
使用时,Mixin类通常在继承列表的第一个位置
父类只做方法的定义
抽象类,抽象方法
其他语言抽象类不可实例化
抽象类一般不实例化,作为基类用
使用Mixin
的有点:
使用Mixin可以在不对类进行任何修改的情况下,扩充功能
可以方便的组织和维护不同功能组件的划分
可以根据需要任意调整功能类的组合
可以避免创建很多新的类,导致类的继承混乱
下面代码中所谓的迷信完全就是父类,只是跟子类或者其他内容的耦合度很低:
# MiuIn案例
class Person(object):
pass
class FWordMixin():
def fxxk(self):
print("这特么地........")
class HappyMixin():
def happy(self):
print("娃哈哈哈哈哈哈........")
class TeacherWithNothing(Person):
'''
一个冰冷的莫得感情的老师
'''
name = "别的老师"
def work(self):
print("上课啦.....")
class TeacherWithHappy(Person, HappyMixin):
'''
一个快乐的老师
'''
name = "别的老师"
def work(self):
print("上课啦.....")
class LiuDana(Person, FWordMixin):
'''
大拿上课老骂人, 这不好,阿弥陀佛
'''
name = "别的老师"
def work(self):
print("上课啦.....")
nothing = TeacherWithNothing()
nothing.work()
happy = TeacherWithHappy()
happy.work()
happy.happy()
dana = LiuDana()
dana.fxxk()
运行结果如下:
Connected to pydev debugger (build 211.7142.13)
上课啦.....
上课啦.....
娃哈哈哈哈哈哈........
这特么地........
上面代码可以看出, 老师类的高兴和骂人功能,只是一个迷信,如果需要,只是简单的继承自这个迷信即可.
迷信就是特殊的父类,且一般不把他看做父类,只看着功能的横向扩展
2.7. 类相关函数¶
在对类的使用中, 我们可能需要对类进行判断甚至控制,此时我们可以借助于下面的一些函数进行.
issubclass
:检测一个类是否是另一个类的子类isinstance
:检测一个对象是否是一个类的实例hasattr
:检测一个对象是否由成员xxxgetattr
: get attributesetattr
: set attributedelattr
: delete attributedir
: 获取对象的成员列表
代码简单,参看下面案例即可:
class A():
pass
class B(A):
def __init__(self):
self.name = "刘大拿"
self.tel = '131 191 44223'
self.qq_group = "9990960"
def say(self):
print("Hello ......")
# 第一个参数是判断的类名, 第二个参数是可能的父类组成的tuple`
print("B是A的子类: ", issubclass(B, (A, )))
b = B()
print('b是B的实例:', isinstance(b, B))
# 第二个参数字符串形式的属性名称
print("B是否具有属性say:", hasattr(b, "say"))
# 利用字符串得到某个具体的函数或者功能
# 这个在系统级别的代码中常用
say = getattr(b, "say")
# 注意这个调动方式, say必须要求有一个参数,但此时这个函数代表的是b实例的函数say,此种方式调用照样默认传入b作为第一个参数
say()
# 给实例或者类设置内容
b = B()
# 给实例b设置一个属性age
setattr(b, "age", 18)
print(b.age)
def sayage(self, age):
print("AGE.......", age)
# 同样可以把函数设置给b,但此时这个函数不作为b的实例函数, 调用的时候也不默认把自身当做第一个参数传入
setattr(b, "sayage", sayage)
b.sayage(b, 23)
# 显示类,实例所有可用内容,属性
print(dir(b))
print(dir(B))